#!/usr/bin/env perl 

# Generate partitions
# Basile Clout, October 2007

use Getopt::Long;
use IPC::Open3;

use Statistics::Descriptive;
use File::Basename;
use File::Copy;
use Cwd 'abs_path';

use strict;
use warnings;


sub usage(){
  print <<EOF;

Usage: ./generate_part.pl -p partitioner (pagrid or metis) [-d
temporary folder where partitions are created] [-n number of
partitions created ] [-o file containing the names of the created
partitions] [-l log file] [-r res file] [--name Base name of the
generated partitions] [--list text file containing some special
partitions] [-G G pagrid parameter] [-h print this help] <.graph file>
<processor graph (integer if metis, processor graph if pagrid, etc.) >

EOF
exit;
}

my $logfile="generateParts.log";
my $resfile="generateParts.res";

#my $pagrid_bin="pagrid";
my $pagrid_bin="/root/export_cluster/code/partitioners/pagridL/src/pagrid";
my $pagrid_fl="-r";

#my $metis4_bin="pmetis";
my $metis4_bin="/root/export_cluster/code/partitioners/metis/metis-4.0/pmetis";
my $metis4_fl="";

my $statPart_bin="/root/export_cluster/code/tools/cutedges/statPart.pl";

my $part="";
my ($part_bin, $part_fl, $part_arg);
my $G=10;

my ($fgraph, $grid);

my $help;

my $folder=".";
my $nb_part=2;			# Number of partitions to create
my $nb_draw=0;			# Number of printed graphs

my $new_name="";
my $list="";
my $cur_name;
my $flist="";

# Get options
GetOptions("p=s"=>\$part, "d=s"=>\$folder, "n=i"=>\$nb_part, "l=s"=>\$logfile, "r=s"=>\$resfile, "o=s"=>\$list, "h"=>\$help, "name=s"=>\$new_name, "pagrid_flags=s"=>\$pagrid_fl, "list=s"=>\$flist, "G=i"=>\$G);

$pagrid_fl.=" -G $G" if ($G);
if ($#ARGV != 1 || $help){
  print "ERROR: $0 needs 2 arguments ($#ARGV given).\n";
  usage();
}else{
  $fgraph=$ARGV[0];
  $grid=$ARGV[1];
}

if (! -r $fgraph){
  die("Impossible to read graph file $fgraph");
}

if ($part =~ /^pagrid/){
  $part_bin=$pagrid_bin;
  $part_fl=$pagrid_fl;
  if (-r $grid){
    $part_arg=$grid;
  }else{
    die("Impossible to read processor graph file $grid");
  }
}elsif ($part =~ /^metis/){
  $part_bin=$metis4_bin;
  $part_fl=$metis4_fl;
  if ( $grid !~ /\d+/){
    die('If the partitioner is metis, enter an integer number of partitions');
  }else{
    $part_arg=$grid;
  }
}elsif ($part eq ""){
  print "Please enter a partitioner: -p metis/pagrid";
  usage();
}
else{
  die("Unknown partitioner $part");
}

if ($folder !~ /^\.\//){
  $folder="./$folder";
}
if ($folder =~ /(.*)\/$/){
  $folder=$1;
}
if (! -d $folder){
  mkdir $folder;
}

my ($graph_name, $dummy) = fileparse($fgraph);
my $grid_name;

my $created_name;
if ($part =~ /^pagrid/){
  ($grid_name, $dummy) = fileparse($grid); 
  $created_name="${graph_name}.${grid_name}.result"; 
  $new_name="${graph_name}.$part.${grid_name}part" if ($new_name eq "");
}elsif($part =~ /^metis/){
  $created_name="${fgraph}.part.${grid}";
  $new_name="${graph_name}.$part.${part_arg}parts" if ($new_name eq "");
}

if($list ne ""){
  open LIST, ">$list" or die("Impossible to create list file");
}
  
# Quickly check options
die("$fgraph graph file does not exist!") unless (-e $fgraph);
die("$part_arg processor graph does not exist!") if ( (! -e $part_arg) && ($part eq "pagrid") );

my ($sec, $min, $hrs, $day, $month, $year) = (localtime) [0,1,2,3,4,5];
my $date = sprintf("%04d-%02d-%02d %d:%d:%d\n", $year+1900, $month+1, $day, $hrs, $min, $sec);

# Create files
open LOG, ">${folder}/$logfile" or die("ERROR: Impossible to create logfile $logfile: $!");
print LOG "Log file Generated automatically by generate_part.pl on $date\n Basile Clout, October 2007\n\n";
open RES, ">${folder}/$resfile" or die("ERROR: Impossible to create resfile $resfile: $!");
print RES "# Res file Generated automatically by generate_part.pl on $date\n# Basile Clout, October 2007\n\n";

my $nb_del = unlink <$folder."/".$new_name*>;
print LOG "Delete $nb_del files starting with $new_name \n";


my $order="$part_bin $part_fl $fgraph $part_arg";
my @glob_arr=();
my @proc_arr=();
my $proc_arr_ref;
my $loc_arr_ref;
my $vert_nb;
my $proc_nb;
my @names;

print LOG "Order: $order\n\n";

print RES "# \@'num': test number\n";
print RES "# proc# #vertices time1 time2 adj\n";

# Process each partition
for (my $i=0; $i<$nb_part; $i++) {

  print LOG "#### TEST", $i+1, " #######################\n\n";
  print RES "\n\@",$i+1,"\n";

  # Order
  $vert_nb=0;
  $proc_nb=0;
  my $pid = open PARTITIONER, "$order |" or die("ERROR: Impossible to run partitioner with order $order: $!");
  my @partitioner_content=<PARTITIONER>;
  print LOG join(' ', @partitioner_content);

  if($part eq 'pagrid'){

    # Print relevant stats in the res file
    foreach (@partitioner_content) {
      # Reg exp all the lines take the interesting values
      if (/processor\s+(\d) :\s+(\d+) vertices\s+(\d+\.\d+)\s+\(\s+(\d+\.\d+)\) execution time adj\s+:\s+([\d ]+)/) {
	$glob_arr[$i][$1] = [ $1, $2, $3, $4, $5 ];
	$vert_nb+=$2;
	$proc_nb++;
      }
    }

    for my $k (0 .. $proc_nb-1) {
      for (0 .. 2) {
	print RES "$glob_arr[$i][$k][$_]\t";
      }
      print RES "\n"
    } 
  }
  
  $cur_name=$new_name.".".scalar($i+1);
  rename $created_name, $cur_name or warn "Could not rename $created_name to $new_name: $!\n";
  move $cur_name, $folder;
  print LIST abs_path("${folder}/${cur_name}\n") unless ($list eq "");
  $names[$i]=abs_path("${folder}/${cur_name}\n");
}

# Some laundry for pagrid
if ($part =~ /^pagrid/){
  unlink "${graph_name}.${grid_name}.time.log" or warn "Could not delete ${graph_name}.${grid_name}.time.log: $!";
  unlink "${graph_name}.${grid_name}.log" or warn "Could not delete ${graph_name}.${grid_name}.log: $!";

}

# Print stats summary in the res file
if ($part =~ /pagrid/){

  print RES "\n# \@stats #tests #procs\n";
  print RES "\@stats ", $#glob_arr+1," $proc_nb\n";
  print RES "# proc #avg_vertices(min max std_dev) #avg_t1(min max std_dev) #avg_t2(min max std_dev)\n\n";
  
  my $stat;
  # Stat for each line (processor)
  for my $i (0 .. $proc_nb-1) {
    
    my @vert_arr;
    my @t1_arr;
    my @t2_arr;
    
    for (0 .. $#glob_arr) {
      $vert_arr[$_] = $glob_arr[$_][$i][1];
      $t1_arr[$_] = $glob_arr[$_][$i][2];
      $t2_arr[$_] = $glob_arr[$_][$i][3];
    }
    
    # Stats for vertices
    $stat = Statistics::Descriptive::Sparse->new();
    $stat->add_data(@vert_arr);
    my $vert_min = $stat->min();
    my $vert_max = $stat->max();
    my $vert_avg = $stat->sum()/$stat->count();
    my $vert_std = 0;
    if($#glob_arr>0){
      $vert_std = $stat->standard_deviation();
    }
    
    # Stats for time1
    $stat = Statistics::Descriptive::Sparse->new();
    $stat->add_data(@t1_arr);
    my $t1_min = $stat->min();
    my $t1_max = $stat->max();
    my $t1_avg = $stat->sum()/$stat->count();
    my $t1_std = 0;
    if($#glob_arr>0){
      $t1_std = $stat->standard_deviation();
    }
    
    # Stats for time2
    $stat = Statistics::Descriptive::Sparse->new();
    $stat->add_data(@t2_arr);
    my $t2_min = $stat->min();
    my $t2_max = $stat->max();
    my $t2_avg = $stat->sum()/$stat->count();
    my $t2_std = 0;
    if($#glob_arr>0){
      $t2_std = $stat->standard_deviation();
    }
  
    printf RES "%d %.2f(%d %d %.2f) %.2f(%.2f %.2f %.2f) %.2f(%.2f %.2f %.2f)\n",$i, $vert_avg, $vert_min, $vert_max, $vert_std, $t1_avg, $t1_min, $t1_max, $t1_std, $t2_avg, $t2_min, $t2_max, $t2_std;
    #printf "%d %.2f(%d %d %.2f) %.2f(%.2f %.2f %.2f) %.2f(%.2f %.2f %.2f)\n",$i, $vert_avg, $vert_min, $vert_max, $vert_std, $t1_avg, $t1_min, $t1_max, $t1_std, $t2_avg, $t2_min, $t2_max, $t2_std;
    printf "%d\t%.2f (%d %d %.2f)\t%.2f (%.2f %.2f %.2f)\n",$i, $vert_avg, $vert_min, $vert_max, $vert_std, $t1_avg, $t1_min, $t1_max, $t1_std;
  }

  # Find the best (minimum max EET) and worst (maximum max EET) partitions
  
  my @part_arr;
  for my $i (0..$#glob_arr){
    my $partimax=0;
    for my $j (0..$proc_nb-1){
      if($glob_arr[$i][$j][2]>$partimax){
	$partimax=$glob_arr[$i][$j][2];
      }
    }
    $part_arr[$i]=$partimax;
  }
  $stat = Statistics::Descriptive::Full->new();
  $stat->add_data(@part_arr);
  
  my ($partmax, $partmin)=($stat->max(), $stat->min());
  my ($bestpart, $worstpart)=($stat->mindex(), $stat->maxdex());
  my $partmedian = $stat->median();
  my $medianpart=0;

  for my $i (0..$#part_arr){
    if ($part_arr[$i]==$partmedian){
      $medianpart=$i;
    }
  }
  
  print "Best part\t".eval($bestpart+1)."/".eval($#part_arr+1)."\t(maxEET=$partmin)\n";
  printf "Median part\t".eval($medianpart+1)."/".eval($#part_arr+1)."\t(maxEET=$partmedian)\n";
  print "Worst part\t".eval($worstpart+1)."/".eval($#part_arr+1)."\t(maxEET=$partmax)\n";
  my @special=($bestpart, $medianpart, $worstpart);
  open(MYLIST, ">>$flist") or die "Impossible to open $flist for appending: $!";
  for my $k (@special) {
    print "\@".eval($k+1)."\n";
    my $statOrder="$statPart_bin --eet --ce $fgraph $part_arg $names[$k]";
    system($statOrder);
    print MYLIST $names[$k];
    for my $i (0..$proc_nb-1){
      for (0 .. 2) {
	print "$glob_arr[$k][$i][$_]\t";
      }
      print "\n"
    } 
  }
}


